﻿using System;
using System.Collections.Generic;
using System.Configuration;
using System.Diagnostics;
using System.Linq;
using System.Messaging;
using System.Threading;
using MiniStock.Entities.Common.Events;
using MiniStock.Infrastructure;
using MiniStock.Infrastructure.Configurator;
using MiniStock.Infrastructure.Queue;

namespace MiniStock.BusinessLogic.EventQueue.Queue
{
    public class MsmqQueue: IEventQueue
    {
        public int ReadTimeout { get; set; }

        #region IEventQueue Members

        public string Address { get; set; }

        public EventBase Dequeue(string clientHash = null)
        {
            using (var mq = new MessageQueue(Address, true) { UseJournalQueue = true, })
            {
                mq.Formatter = new XmlMessageFormatter(new[] {typeof(string)});
                Message result = null;
                int sleep = 100;

                for (int i = 0; i < 3;i++ )
                    try
                    {
                        result = mq.Receive(TimeSpan.FromMilliseconds(ReadTimeout));
                        break;
                    }
                    catch (MessageQueueException ex)
                    {
                        if (ex.MessageQueueErrorCode == MessageQueueErrorCode.IOTimeout)
                            break;
                        if(i == 2)
                            Trace.TraceError("Error receiving message from queue {0}: {1}", Address, ex);
                        else
                            Trace.TraceWarning("Error receiving message from queue {0}: {1}", Address, ex);

                        Thread.Sleep(sleep);
                        sleep *= 2;
                    }
                if (result == null)
                    return null;

                var message = ((string) result.Body).ToEventBase();
                Trace.CorrelationManager.ActivityId = message.CorrelatedEventId ?? message.EventId;
                Trace.TraceInformation("Dequeue {0}: {1} (id={2}, correlationid={3})", Address, message, message.EventId, message.CorrelatedEventId);
                return message;
            }
        }

        public void Enqueue(EventBase item)
        {
            Trace.TraceInformation("Enqueue {0}: {1} (id={2}, correlationid={3})", Address, item, item.EventId, item.CorrelatedEventId);

            using (var mq = new MessageQueue(Address, true))
            {
                var msg = new Message
                            {
                                Body = item.ToXmlString(),
                                Label = item.EventId.ToString(),
                            };
                //if (item.CorrelatedEventId != null)
                //    msg.CorrelationId = item.CorrelatedEventId.ToString();
                int sleep = 100;
                for (int i = 0; i < 3; i++)
                {
                    try
                    {
                        mq.Send(msg);
                        break;
                    }
                    catch (Exception ex)
                    {
                        if(i==2)
                            Trace.TraceError("Error sending message to queue {0} id: {1}, {2}", Address, item.EventId, ex);
                        else
                            Trace.TraceWarning("Error sending message to queue {0} id: {1}, {2}", Address, item.EventId, ex);

                        Thread.Sleep(sleep);
                        sleep *= 2;
                    }
                }
            }
        }

        public bool HasEvent()
        {
            using (var mq = new MessageQueue(Address))
            {
                try
                {
                    return mq.Peek(TimeSpan.FromMilliseconds(ReadTimeout)) != null;
                }
                catch (MessageQueueException ex)
                {
                    if(ex.MessageQueueErrorCode == MessageQueueErrorCode.IOTimeout)
                        return false;

                    throw;
                }
            }
        }

        public void Configure(IEventQueueConfigurator configurator, string queueName)
        {
            Address = configurator.GetQueueAddress(queueName);

            int d;
            if (Int32.TryParse(ConfigurationManager.AppSettings["MsmqQueue." + Address + ".ReadTimeoutMs"], out d))
                ReadTimeout = d;
            else if (Int32.TryParse(ConfigurationManager.AppSettings["MsmqQueue.ReadTimeoutMs"], out d))
                ReadTimeout = d;
            else
                ReadTimeout = 100;

            if (!MessageQueue.Exists(Address))
            {
                Trace.TraceInformation("Creating message queue: {0}", Address);
                MessageQueue.Create(Address);
            }
        }

        public void CleanupClientAffinity(string clientId)
        {
        }

        public List<EventBase> CollectEvents(DateTime from, string entityType = null, string entityHash = null)
        {
            throw new NotImplementedException();
        }

        public IEnumerable<EventBase> AsEnumerable()
        {
            return new List<EventBase>();
            using(var mq = new MessageQueue(Address, QueueAccessMode.Peek))
            {
                var result =
                    (from e in mq.GetAllMessages()
                     select ((string) e.Body).ToEventBase()).ToList();
                return result;
            }
        }

        public int Count()
        {
            return AsEnumerable().Count();
        }

        #endregion
    }
}
